#include "KvTable.h"

#include <cassert>
//#include <iostream>

#include "helper/sqlite/DB.h"
#include "helper/sqlite/Statement.h"
#include "helper/sqlite/Error.h"

namespace memdb {
namespace impl {
namespace simple {

using DB = helper::sqlite::DB;
using Statement = helper::sqlite::Statement;
using QueryResult = helper::sqlite::QueryResult;

static inline std::string __tableFilePath(std::filesystem::path & dir, const std::string & name){
	return (dir / (name + ".db")).string();
}

KvTable::KvTable(const std::filesystem::path & dir, const std::string & name)
: _name(name)
, _dir(dir)
, _filePath(__tableFilePath(_dir, _name))
{
}

KvTable::~KvTable()
{
}


bool KvTable::exists() const{
	return std::filesystem::exists(_filePath);
}
std::error_code KvTable::destroy(){
	if(isOpened()){
		if(auto && ec = close(); ec) 
			return ec;
	}

	std::filesystem::remove(_filePath);
  return std::error_code();
}


std::error_code KvTable::_open(){
  return  exists() ? openExists() : create(); 
}

std::error_code KvTable::_close(){
	_records.reset();

	if(auto && ec = _db->close(); ec) return ec;
	_db.reset();
  return std::error_code();
}

std::error_code KvTable::createTable(){
  std::string sql;
  sql.append("create table ").append(_name).append(" (key text Primary key, value blob);");

	std::error_code ec;
	auto && stmt = _db->createStatement(sql, ec);
	if(!ec && stmt) {
		auto && r = stmt->action();
		return r? std::error_code() : r.errorCode();
	}
	return ec;
}

//precondition: _filePath ot exists
std::error_code KvTable::create(){

  std::filesystem::create_directories(_dir);// create parent dir if not exist

	_db.reset(new DB(_filePath));//create db;
	if(auto && ec = _db->open(); ec)
		return ec;

	auto && ec = createTable();
	if(ec) return ec;

	_records.reset(new Records(*this)); //create records
	return ec;
}

//ASSERT(db.isOpened() == true)
bool KvTable::existsTable() const{
    std::string sql;
    sql.append("select 1 from ").append(_name).append(" limit 1 offset 0;");
    
    std::error_code ec;
    auto && stmt = _db->createStatement(sql, ec); // table not exist =>  ec==(1:SQL logic error)
    if(!ec && stmt){
      return stmt->query().errorCode() == helper::sqlite::error::done; // table exist => sqlite return done
    }
    return false;
}
std::error_code KvTable::dropTable(){
    std::string sql;
    sql.append("drop table ").append(_name);

    std::error_code ec;
    auto && stmt = _db->createStatement(sql, ec);
    if(!ec && stmt){
      auto && r = stmt->action();
      return r ? std::error_code() : r.errorCode();
    }
    return ec;
}
//precondition: _filePath exists
std::error_code KvTable::openExists(){

	_db.reset(new DB(_filePath));//open exists db;
	if(auto && ec = _db->open(); ec) 
		return ec;

	if(!existsTable()){ // table not exist => create it
		if(auto && ec = createTable(); ec){
			return ec;
		}
	}

	_records.reset(new Records(*this)); //create records
	return std::error_code();
}

KvTable::Records & KvTable::records() const {
	return *_records;
}






KvTable::Records::Records(KvTable & table)
: _table(table)
{
}
KvTable::Records::~Records(){
}

std::error_code KvTable::Records::insert(const std::string & key, const std::string & value){
  std::string sql;
  sql.append("insert into ").append(_table.name()).append(" values(?, ?);");
	
	std::error_code ec;
  auto && stmt = _table._db->createStatement(sql, ec);
	if(!ec && stmt){
		if(ec = stmt->textBind(1, key); ec) 
			return ec;
		if(ec = stmt->binaryBind(2, value); ec)
			return ec;
		auto && r = stmt->execute();
		return r? std::error_code() : r.errorCode();
	}
	return ec;
}
std::error_code KvTable::Records::replace(const std::string & key, const std::string & value){
  std::string sql;
  sql.append("replace into ").append(_table.name()).append(" values(?, ?);");

	std::error_code ec;
  auto && stmt = _table._db->createStatement(sql, ec);
	if(!ec && stmt){
		if(ec = stmt->textBind(1, key); ec) 
			return ec;
		if(ec = stmt->binaryBind(2, value); ec)
			return ec;
		auto && r = stmt->execute();
		return r? std::error_code() : r.errorCode();
	}
	return ec;
}
std::error_code KvTable::Records::remove(const std::string & key){
  std::string sql;
  sql.append("delete from ").append(_table.name()).append(" where key = ?;");

	std::error_code ec;
  auto && stmt = _table._db->createStatement(sql, ec);
	if(!ec && stmt){
		if(ec = stmt->textBind(1, key); ec) 
			return ec;

		auto && r = stmt->execute();
		return r? std::error_code() : r.errorCode();
	}
	return ec;
}
std::error_code KvTable::Records::update(const std::string & key, const std::string & value){
  std::string sql;
  sql.append("update ").append(_table.name()).append(" set value = ? where key = ?;");

	std::error_code ec;
  auto && stmt = _table._db->createStatement(sql, ec);
	if(!ec && stmt){

		if(ec = stmt->binaryBind(1, value); ec) 
			return ec;
		if(ec = stmt->textBind(2, key); ec) 
			return ec;

		auto && r = stmt->execute();
		return r? std::error_code() : r.errorCode();
	}
	return ec;
}


bool KvTable::Records::exists(const std::string & key) const{
  std::string sql;
  sql.append("select count(1) from ").append(_table.name()).append(" where key = ?;");

	std::error_code ec;
  auto && stmt = _table._db->createStatement(sql, ec);
	if(!ec && stmt){
		if(stmt->textBind(1, key)) return false;
		
		auto && qr = stmt->query();
		if(qr){
			auto && cell = qr.values(0);
			assert(cell);
			return 1 == cell.value().integer();
		}
		return false;
	}
	return false;
}

std::error_code KvTable::Records::get(const std::string & key, std::string & value) const{
  std::string sql;
  sql.append("select value from ").append(_table.name()).append(" where key = ?;");

	std::error_code ec;
  auto && stmt = _table._db->createStatement(sql, ec);
	if(!ec && stmt){
		if(ec = stmt->textBind(1, key); ec) 
			return ec;
		
		auto && qr = stmt->query();
		if(qr){
			auto && cell = qr.values(0);
			assert(cell);
			cell.value().binary(value);
			return std::error_code();
		}
		return qr.errorCode();
	}
	return ec;
}

size_t KvTable::Records::count() const{
  std::string sql;
  sql.append("select count(1) from ").append(_table.name());

	std::error_code ec;
  auto && stmt = _table._db->createStatement(sql, ec);
	if(!ec && stmt){
		auto && qr = stmt->query();
		if(qr){
			auto && cell = qr.values(0);
			assert(cell);
			return cell.value().integer();
		}
		return 0;
	}
	return 0;
}

namespace __internal {

	using DB = helper::sqlite::DB;

	using KvTable = memdb::impl::simple::KvTable;
  class Iterator : public helper::iter::Iterator<KvTable::Records::Item> {
  public:
    Iterator(KvTable & table, const std::string & sql) 
		: _table(table)
    , _item(nullptr)
		{
      auto && stmt = _table._handle().createStatement(sql, _ec);
      assert(stmt && !_ec);
      auto && r = stmt->query();


		  _stmt = std::move(stmt);
		  _result.reset(new QueryResult(std::move(r)));
		}
    ~Iterator() override {
			if(_item != nullptr){
				delete _item;
			}
		}
		std::optional<KvTable::Records::Item> next() override{
			if(*_result) {
				if(_item != nullptr){
					delete _item;
					_item = nullptr;
				}

				auto && key = _result->values(0).value().string();
				auto && value = _result->values(1).value().string();
				_item = new std::pair<const std::string, std::string>(std::move(key), std::move(value));

				_result->next();
				return std::make_optional(_item);
			}
			return std::nullopt;
		}
  private:
		const KvTable & _table;
		KvTable::Records::Item _item;

		std::error_code _ec;
		std::optional<helper::sqlite::Statement> _stmt;
		std::unique_ptr<helper::sqlite::QueryResult> _result;
  };

	std::unique_ptr<helper::iter::Iterator<KvTable::Records::Item>> iterator(KvTable & table, const std::string & sql) {
		return std::unique_ptr<helper::iter::Iterator<KvTable::Records::Item>>(new __internal::Iterator(table, sql));
	}
}//namespace __internal

std::unique_ptr<helper::iter::Iterator<KvTable::Records::Item>> KvTable::Records::iterator() const{
	std::string sql;
  sql.append("select key, value from ").append(_table.name())
  .append(" order by key");
	return __internal::iterator(_table, sql);
}
std::unique_ptr<helper::iter::Iterator<KvTable::Records::Item>> KvTable::Records::iterator(size_t offset, size_t len) const{
  std::string sql;
  sql.append("select key, value from ").append(_table.name())
    .append(" order by key")
    .append(" limit ").append(std::to_string(len))
    .append(" offset ").append(std::to_string(offset));
	return __internal::iterator(_table, sql);
}
std::unique_ptr<helper::iter::Iterator<KvTable::Records::Item>> KvTable::Records::iterator(const std::string & key, size_t offset, size_t len) const{
	std::string sql;
  sql.append("select key, value from ").append(_table.name())
    .append(" where key >='").append(key).append("'")
    .append(" order by key")
    .append(" limit ").append(std::to_string(len))
    .append(" offset ").append(std::to_string(offset));
	return __internal::iterator(_table, sql);
}


}//namespace simple
}//namespace impl
}//namespace memdb
